<script setup lang="ts">
import { ref } from 'vue'

const config = {
  hostUrl: 'wss://ise-api.xfyun.cn/v2/open-ise',
  host: 'ise-api.xfyun.cn',
  appid: '1c95f081',
  apiSecret: 'YzQwMGRkM2JkNjVlN2ZlMmU1NjA3MThi', 
  apiKey: '3d27ee35539db9421230aac43728e19f', 
  uri: '/v2/open-ise'
}

const isRecording = ref(false)
const score = ref('')

let mediaStream: MediaStream
let audioContext: AudioContext
let processor: ScriptProcessorNode
let websocket: WebSocket

const startRecording = async () => {
  if (isRecording.value) {
    return
  }
  isRecording.value = true

  try {
    mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })

    audioContext = new AudioContext({ sampleRate: 16000 })
    const source = audioContext.createMediaStreamSource(mediaStream)

    processor = audioContext.createScriptProcessor(4096, 1, 1)

    source.connect(processor)
    processor.connect(audioContext.destination)

    processor.onaudioprocess = (e) => {
      const data = e.inputBuffer.getChannelData(0)
      const pcmData = floatTo16BitPCM(data)
      sendData(pcmData)
    }

    await initWebSocket()
  } catch (err) {
    console.error('Error accessing microphone', err)
    isRecording.value = false
  }
}

const stopRecording = () => {
  if (!isRecording.value) {
    return
  }
  isRecording.value = false

  if (processor && audioContext) {
    processor.disconnect()
    audioContext.close()
  }

  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => track.stop())
  }

  sendLastFrame()
}

function floatTo16BitPCM(input: Float32Array): Int16Array {
  const output = new Int16Array(input.length)
  for (let i = 0; i < input.length; i++) {
    let s = Math.max(-1, Math.min(1, input[i]))
    output[i] = s < 0 ? s * 0x8000 : s * 0x7fff
  }
  return output
}

function arrayBufferToBase64(buffer: ArrayBuffer): string {
  let binary = ''
  const bytes = new Uint8Array(buffer)
  const len = bytes.byteLength
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i])
  }
  return btoa(binary)
}

const initWebSocket = async () => {
  const date = new Date().toUTCString()
  const authStr = await getAuthStr(date)
  const url = `${config.hostUrl}?authorization=${authStr}&date=${encodeURIComponent(date)}&host=${config.host}`

  websocket = new WebSocket(url)

  websocket.onopen = () => {
    console.log('WebSocket connection established.')
    sendFirstFrame()
  }

  websocket.onmessage = (event) => {
    const res = JSON.parse(event.data)
    if (res.code !== 0) {
      console.error(`Error code ${res.code}, reason ${res.message}`)
      return
    }

    if (res.data && res.data.status === 2) {
      const data = res.data.data
      const decodedData = atob(data)
      console.log(decodedData)
      // const result = JSON.parse(decodedData)
      // console.log('Final result:', result)
      // score.value = result.practice.total_score.toFixed(2)
    }
  }

  websocket.onerror = (err) => {
    console.error('WebSocket error:', err)
  }

  websocket.onclose = () => {
    console.log('WebSocket connection closed.')
  }
}

async function getAuthStr(date: string): Promise<string> {
  const signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`
  const encoder = new TextEncoder()
  const keyData = encoder.encode(config.apiSecret)
  const signatureData = encoder.encode(signatureOrigin)

  const cryptoKey = await crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )

  const signature = await crypto.subtle.sign('HMAC', cryptoKey, signatureData)
  const signatureBase64 = arrayBufferToBase64(signature)

  const authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signatureBase64}"`
  const authStr = btoa(authorizationOrigin)
  return authStr
}

const sendFirstFrame = () => {
  const frame = {
    common: { app_id: config.appid },
    business: {
      sub: 'ise',
      ent: 'cn_vip',
      category: 'read_sentence',
      text: `\uFEFF${'你好'}`,
      tte: 'utf-8',
      rstcd: 'utf8',
      ttp_skip: true,
      cmd: 'ssb',
      aue: 'raw',
      auf: 'audio/L16;rate=16000'
    },
    data: { status: 0 }
  }
  websocket.send(JSON.stringify(frame))
}

const sendData = (pcmData: Int16Array) => {
  if (websocket && websocket.readyState === WebSocket.OPEN) {
    const base64Data = arrayBufferToBase64(pcmData.buffer)

    const frame = {
      common: { app_id: config.appid },
      business: {
        aus: 1,
        cmd: 'auw',
        aue: 'raw'
      },
      data: { status: 1, data: base64Data }
    }
    websocket.send(JSON.stringify(frame))
  }
}

const sendLastFrame = () => {
  if (websocket && websocket.readyState === WebSocket.OPEN) {
    const frame = {
      common: { app_id: config.appid },
      business: {
        aus: 4,
        cmd: 'auw',
        aue: 'raw'
      },
      data: { status: 2, data: '' }
    }
    websocket.send(JSON.stringify(frame))
  }
}
</script>

<template>
  <n-card style="margin-top: 3vh">
    <n-button @click="isRecording ? stopRecording() : startRecording()">
      {{ isRecording ? '停止录音' : '录音' }}
    </n-button>
    <div>分数：{{ score }}</div>
  </n-card>
</template>
